package nl.weeaboo.sh.editor.area;

import java.awt.AWTException;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Map;

import javax.swing.JPanel;

import nl.weeaboo.sh.level.Level;
import nl.weeaboo.sh.model.AreaModel;
import nl.weeaboo.sh.model.RoomEnemyModel;
import nl.weeaboo.sh.model.RoomModel;
import nl.weeaboo.sh.model.TileSetIndex;
import timon.common.GraphicsUtil;

@SuppressWarnings("serial")
public class TilePanel extends JPanel {

	private static final float zoomTable[] = new float[] {
		0.05f, 0.10f, 0.25f, 0.50f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f
	};

	private Cursor hiddenCursor;
	private RandomColors randomColors;
	private Point clickPoint;
	private BufferedImage image;
	private Palette palette;
	
	private AreaModel area;
	private Map<String, EditorTileSet> tileSets;
	private Point selectedPos;
	private TileSetIndex selectedTileIndex;
	private RoomModel selectedRoom;
	private RoomEnemyModel selectedEnemy;
	private float tx, ty;
	private int zoom;
	private boolean drawBackground, drawForeground;
	private Font font, smallFont;
	
	public TilePanel(RandomColors rc, Palette palette) {
		this.palette = palette;
		
		font = new Font("tahoma", Font.PLAIN, 12);
		smallFont = new Font("tahoma", Font.PLAIN, 10);
		
		randomColors = rc;		
		zoom = 5;
		drawBackground = true;	
		
		hiddenCursor = Toolkit.getDefaultToolkit().createCustomCursor(
				new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB),
				new Point(0, 0), "invisibleCursor");
		
		addInputListeners();
		
		setPreferredSize(new Dimension(800, 480));
		setMaximumSize(getPreferredSize());
		
		palette.createNorthWestPanel(this);
	}
	
	//Functions	
	private void addInputListeners() {
		addMouseListener(new MouseListener() {
			public void mouseClicked(MouseEvent e) {
			}
			public void mouseEntered(MouseEvent e) {
				requestFocus();
				updateCursor(e);
			}
			public void mouseExited(MouseEvent e) {
				selectedPos = null;
				updateCursor(e);
				repaint();
			}
			public void mousePressed(MouseEvent e) {
				clickPoint = e.getPoint();
				updateCursor(e);
			}
			public void mouseReleased(MouseEvent e) {
				clickPoint = null;
				updateCursor(e);
			}
		});
		addMouseMotionListener(new MouseMotionListener() {
			public void mouseDragged(MouseEvent e) {
				if (clickPoint == null) return;
				
				updateCursor(e);

				if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
					tx += (e.getX() - clickPoint.x) * getZoom();
					ty += (e.getY() - clickPoint.y) * getZoom();
					
					if (!contains(e.getPoint())) {
						int x = e.getX();
						int y = e.getY();
						if (e.getX() < 0) x = getWidth() + e.getX();
						if (e.getX() >= getWidth()) x = e.getX() - getWidth() - 1;
						if (e.getY() < 0) y = getHeight() + e.getY();
						if (e.getY() >= getHeight()) y = e.getY() - getHeight() - 1;
					
						Point los = getLocationOnScreen();
						clickPoint = new Point(x, y);
						try {
							Robot r = new Robot();
							r.mouseMove(los.x + x, los.y + y);
						} catch (AWTException awte) {							
						}
					} else {
						clickPoint = e.getPoint();
					}
					
					redraw();
				}
			}
			public void mouseMoved(MouseEvent e) {
				updateCursor(e);
			}
		});
		
		setFocusable(true);
		addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				float oldZoom = zoomTable[zoom];

				int key = e.getKeyCode();
				if (key == KeyEvent.VK_ADD) {
					
					if (zoom < zoomTable.length-1) {
						zoom++;
					}
					
					tx = zoomTable[zoom] * (tx / oldZoom);
					ty = zoomTable[zoom] * (ty / oldZoom);
					
					redraw();
				} else if (key == KeyEvent.VK_SUBTRACT) {
					if (zoom > 0) {
						zoom--;
					}
					
					tx = zoomTable[zoom] * (tx / oldZoom);
					ty = zoomTable[zoom] * (ty / oldZoom);

					redraw();
				}
			}
			public void keyReleased(KeyEvent e) {
			}
			public void keyTyped(KeyEvent e) {
			}
		});
	}
	
	public void redraw() {
		if (image == null) return;
		
		int w = getWidth();
		int h = getHeight();
		Graphics2D g = (Graphics2D)image.getGraphics();
		g.setBackground(Color.BLACK);
		g.clearRect(0, 0, w, h);
		g.setClip(0, 0, w, h);
		g.setFont(font);
		
		AffineTransform oldTransform = g.getTransform();
		
		if (area != null) {
			g.translate(tx, ty);
			g.scale(getZoom(), getZoom());
			drawArea(g, area);
		}
		
		g.setTransform(oldTransform);
		
		repaint();
	}
	
	protected void paintComponent(Graphics graphics) {
		super.paintComponent(graphics);
		
		Graphics2D g = (Graphics2D)graphics;		
		if (image != null) {
			g.drawImage(image, 0, 0, this);
		}
		
		int ts = Level.tileSize;
		AffineTransform oldTransform = g.getTransform();
		
		if (area != null) {
			g.translate(tx, ty);
			g.scale(getZoom(), getZoom());

			if (selectedPos != null && getCursor() != hiddenCursor) {
				g.translate(selectedPos.x*ts, selectedPos.y*ts);
				
				Color c = new Color(255, 0, 0);
				g.setColor(GraphicsUtil.setAlpha(c, 150));
				g.fillRect(0, 0, ts, ts);
				g.setColor(c);
				g.drawRect(0, 0, ts-1, ts-1);
				
				RoomModel room = area.getRoom(selectedPos.x, selectedPos.y);
				if (room != null) {
					String string = String.format("(%d, %d)", selectedPos.x-room.getX(),
							selectedPos.y-room.getY());
					
					g.setColor(Color.BLACK);
					g.drawString(string, 1, 1-5);
					g.setColor(Color.WHITE);
					g.drawString(string, 0, -5);
				}

				g.translate(-selectedPos.x*ts, -selectedPos.y*ts);
			}
		}
		
		g.setTransform(oldTransform);
	}
	
	protected void drawArea(Graphics2D g, AreaModel area) {
		for (RoomModel room : area.getRooms()) {
			drawRoom(g, room);
		}
		
		for (int n = 0; n < 2; n++) {
			for (RoomModel room : area.getRooms()) {
				drawRoomOverlay(g, room, n);
			}
		}
	}
	
	protected void drawRoom(Graphics2D g, RoomModel room) {
		int lineHeight = 12;
		int rw = room.getWidth();
		int rh = room.getHeight();
		int ts = Level.tileSize;

		Rectangle cb = g.getClipBounds();
		if (!new Rectangle(room.getX() * ts, room.getY() * ts, rw * ts, rh * ts).intersects(cb)) {
			return;
		}

		g.translate(room.getX() * ts, room.getY() * ts);
		
		//Draw background
		Color c = randomColors.get(room);
		g.setColor(c);
		g.fillRect(0, 0, rw*ts, rh*ts);
						
		//Draw tile images
		int minX = (cb.x - room.getX()*ts) / ts;
		int maxX = (cb.x+cb.width - room.getX()*ts + (ts-1)) / ts;
		int minY = (cb.y - room.getY()*ts) / ts;
		int maxY = (cb.y+cb.height - room.getY()*ts + (ts-1)) / ts;
		minX = Math.max(0, minX);
		minY = Math.max(0, minY);
		maxX = Math.min(room.getWidth()-1, maxX);
		maxY = Math.min(room.getHeight()-1, maxY);
		
		Point p = new Point();
		for (int layer : room.getLayerIndices()) {
			if (layer <= 0 && !drawBackground) continue;
			if (layer >  0 && !drawForeground) continue;
			
			int ty = minY * ts;
			for (p.y = minY; p.y <= maxY; p.y++) {
				int tx = minX * ts;
				for (p.x = minX; p.x <= maxX; p.x++) {
					g.translate(tx, ty);

					TileSetIndex index = room.getTile(layer, p);					
					if (tileSets == null || index == null) {
						//Do Nothing
					} else {
						EditorTileSet tileSet = tileSets.get(index.getTileSet().getId());
						if (tileSet != null) {							
							tileSet.drawTileImage(g, index, 0, 0, ts, ts, this);
						}
					}

					RoomEnemyModel enemy = room.getEnemy(p);
					if (enemy != null) {
						drawEnemy(g, enemy);
					}
					
					g.translate(-tx, -ty);					
					tx += ts;
				}			
				ty += ts;
			}
		}

		//Draw overlay
		Shape oldClip = g.getClip();
		g.clipRect(0, 0, rw*ts, rh*ts);
		
		g.setColor(Color.BLACK);
		g.drawString(room.getId(), 1+10, 1+10+lineHeight);
		g.setColor(Color.WHITE);
		g.drawString(room.getId(), 10, 10+lineHeight);

		g.setClip(oldClip);
				
		g.translate(-room.getX() * ts, -room.getY() * ts);
	}
	
	protected void drawRoomOverlay(Graphics2D g, RoomModel room, int step) {
		int ts = Level.tileSize;
		int grs = ts / 2;
		int rw = room.getWidth();
		int rh = room.getHeight();
		g.translate(room.getX() * ts, room.getY() * ts);

		if (step == 0) {
			//Draw outline
			Color c = randomColors.get(room);
			Stroke oldStroke = g.getStroke();
			g.setStroke(new BasicStroke(5.0f));
			if (room == selectedRoom) {
				g.setColor(Color.WHITE);
			} else {
				g.setColor(c.darker());
			}
			g.drawRoundRect(0, 0, rw*ts, rh*ts, 2, 2);
			g.setStroke(oldStroke);
		}

		if (step == 1) {
			//Draw Open Sides
			Color co = Color.WHITE;
			Color cw = Color.RED;
			if (room == selectedRoom) {
				co = Color.BLUE;
			}
			
			Color cs[][] = new Color[][] {
				new Color[] {GraphicsUtil.setAlpha(co, 200), GraphicsUtil.setAlpha(co, 0)},
				new Color[] {GraphicsUtil.setAlpha(cw, 200), GraphicsUtil.setAlpha(cw, 0)}
			};
			
			int s;
			if ((s = (room.isLeftWall() ? 1 : room.isLeftOpen() ? 0 : -1)) >= 0) {
				g.setPaint(new GradientPaint(0, 0, cs[s][0], grs, 0, cs[s][1]));
				g.fillRect(0, 0, grs, rh*ts);
			}
			if ((s = (room.isRightWall() ? 1 : room.isRightOpen() ? 0 : -1)) >= 0) {
				g.setPaint(new GradientPaint(rw*ts, 0, cs[s][0], rw*ts-grs, 0, cs[s][1]));
				g.fillRect(rw*ts-grs, 0, grs, rh*ts);
			}
			if ((s = (room.isTopWall() ? 1 : room.isTopOpen() ? 0 : -1)) >= 0) {
				g.setPaint(new GradientPaint(0, 0, cs[s][0], 0, grs, cs[s][1]));
				g.fillRect(0, 0, rw*ts, grs);
			}
			if ((s = (room.isBottomWall() ? 1 : room.isBottomOpen() ? 0 : -1)) >= 0) {
				g.setPaint(new GradientPaint(0, rh*ts, cs[s][0], 0, rh*ts-grs, cs[s][1]));
				g.fillRect(0, rh*ts-grs, rw*ts, grs);
			}		
		}
			
		g.translate(-room.getX() * ts, -room.getY() * ts);
	}
	
	protected void drawEnemy(Graphics2D g, RoomEnemyModel enemy) {
		Shape oldClip = g.getClip();
		g.clipRect(0, 0, Level.tileSize, Level.tileSize);
		
		String string = enemy.getClassName().substring(enemy.getClassName().lastIndexOf('.')+1);
		String strings[] = new String[Math.min(3, (string.length()+5) / 6)];
		for (int n = 0; n < strings.length; n++) {
			strings[n] = string.substring(6*n, Math.min(string.length(), 6*(n+1)));
		}
		
		g.setColor(Color.BLUE);
		g.drawRect(0, 0, Level.tileSize-1, Level.tileSize-1);
		
		g.setFont(smallFont);
		for (int i = 0; i < 2; i++) {
			for (int n = 0; n < strings.length; n++) {
				if (i == 0) {
					g.setColor(Color.BLACK);
					g.drawString(strings[n], 3, 9+10*n+1);
				} else if (i == 1) {
					g.setColor(Color.WHITE);
					g.drawString(strings[n], 2, 9+10*n);
				}
			}
		}
		
		g.setFont(font);
		g.setClip(oldClip);
	}
	
	protected void updateCursor(MouseEvent event) {
		updateCursor(event.getPoint(),
				(event.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0,
				(event.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0,
				(event.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0);		
	}
	protected void updateCursor(Point p, boolean ctrlPressed, boolean p1, boolean p2) {
		selectedPos = null;

		setCursor(p2 ? hiddenCursor : null);

		if (area != null) {
			int x = Level.toTileCoord(Math.round((p.x - tx) / getZoom()));
			int y = Level.toTileCoord(Math.round((p.y - ty) / getZoom()));
			
			if (area.getRoom(x, y) != null) {
				selectedPos = new Point(x, y);
				
				if (p1) {
					if (palette.getMode() == Palette.Mode.TILE) {
						if (ctrlPressed) {
							palette.setSelectedTile(area.getTile(getCurrentLayer(), x, y));
						} else {
							area.setTile(getCurrentLayer(), x, y, selectedTileIndex);
							redraw();
						}
					} else if (palette.getMode() == Palette.Mode.ENEMY) {
						if (ctrlPressed) {
							palette.setSelectedEnemy(area.getEnemy(x, y));
						} else {
							area.setEnemy(x, y, selectedEnemy);
							redraw();
						}
					}
				}
				
				repaint();
			}
		}		
	}
	
	//Getters
	protected float getZoom() { return zoomTable[zoom]; }
	protected int getCurrentLayer() { return drawForeground && !drawBackground ? 1 : 0; }
	public boolean getDrawBackground() { return drawBackground; }
	public boolean getDrawForeground() { return drawForeground; }

	//Setters
	public void setArea(AreaModel model, Map<String, EditorTileSet> ts) {
		if (area != model) {
			area = model;
			tileSets = ts;
			selectedPos = null;
			selectedTileIndex = null;
			selectedRoom = null;
			selectedEnemy = null;
			
			tx = ty = 0;
			zoom = 5;
			
			redraw();
		}
	}
	public void setSelectedRoom(RoomModel model) {
		if (selectedRoom != model) {
			selectedRoom = model;
			
			redraw();
		}
	}
	public void setSelectedTileSetIndex(TileSetIndex index) {
		selectedTileIndex = index;
		selectedEnemy = null;
	}

	public void setSelectedEnemy(RoomEnemyModel e) {
		if (selectedEnemy != e) {
			selectedEnemy = e;
			selectedTileIndex = null;
		}
	}

	public void setBounds(int x, int y, int w, int h) {
		super.setBounds(x, y, w, h);

		if (w == 0 || h == 0) {
			image = null;
		} else if (image == null || image.getWidth() != w || image.getHeight() != h) {
			image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
			redraw();
		}
	}

	public void setDrawBackground(boolean d) {
		if (drawBackground != d) {
			drawBackground = d;
			redraw();
		}
	}
	public void setDrawForeground(boolean d) {
		if (drawForeground != d) {
			drawForeground = d;
			redraw();
		}
	}
	
}
